// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/libplatform/tracing/trace-writer.h"

#include <cmath>

#include "base/trace_event/common/trace_event_common.h"
#include "include/v8-platform.h"
#include "src/base/platform/platform.h"

namespace v8 {
namespace platform {
    namespace tracing {

        // Writes the given string to a stream, taking care to escape characters
        // when necessary.
        V8_INLINE static void WriteJSONStringToStream(const char* str,
            std::ostream& stream)
        {
            size_t len = strlen(str);
            stream << "\"";
            for (size_t i = 0; i < len; ++i) {
                // All of the permitted escape sequences in JSON strings, as per
                // https://mathiasbynens.be/notes/javascript-escapes
                switch (str[i]) {
                case '\b':
                    stream << "\\b";
                    break;
                case '\f':
                    stream << "\\f";
                    break;
                case '\n':
                    stream << "\\n";
                    break;
                case '\r':
                    stream << "\\r";
                    break;
                case '\t':
                    stream << "\\t";
                    break;
                case '\"':
                    stream << "\\\"";
                    break;
                case '\\':
                    stream << "\\\\";
                    break;
                // Note that because we use double quotes for JSON strings,
                // we don't need to escape single quotes.
                default:
                    stream << str[i];
                    break;
                }
            }
            stream << "\"";
        }

        void JSONTraceWriter::AppendArgValue(uint8_t type,
            TraceObject::ArgValue value)
        {
            switch (type) {
            case TRACE_VALUE_TYPE_BOOL:
                stream_ << (value.as_bool ? "true" : "false");
                break;
            case TRACE_VALUE_TYPE_UINT:
                stream_ << value.as_uint;
                break;
            case TRACE_VALUE_TYPE_INT:
                stream_ << value.as_int;
                break;
            case TRACE_VALUE_TYPE_DOUBLE: {
                std::string real;
                double val = value.as_double;
                if (/*std::*/isfinite(val)) {
                    std::ostringstream convert_stream;
                    convert_stream << val;
                    real = convert_stream.str();
                    // Ensure that the number has a .0 if there's no decimal or 'e'.  This
                    // makes sure that when we read the JSON back, it's interpreted as a
                    // real rather than an int.
                    if (real.find('.') == std::string::npos && real.find('e') == std::string::npos && real.find('E') == std::string::npos) {
                        real += ".0";
                    }
                } else if (/*std::*/isnan(val)) {
                    // The JSON spec doesn't allow NaN and Infinity (since these are
                    // objects in EcmaScript).  Use strings instead.
                    real = "\"NaN\"";
                } else if (val < 0) {
                    real = "\"-Infinity\"";
                } else {
                    real = "\"Infinity\"";
                }
                stream_ << real;
                break;
            }
            case TRACE_VALUE_TYPE_POINTER:
                // JSON only supports double and int numbers.
                // So as not to lose bits from a 64-bit pointer, output as a hex string.
                stream_ << "\"" << value.as_pointer << "\"";
                break;
            case TRACE_VALUE_TYPE_STRING:
            case TRACE_VALUE_TYPE_COPY_STRING:
                if (value.as_string == nullptr) {
                    stream_ << "\"nullptr\"";
                } else {
                    WriteJSONStringToStream(value.as_string, stream_);
                }
                break;
            default:
                UNREACHABLE();
                break;
            }
        }

        void JSONTraceWriter::AppendArgValue(ConvertableToTraceFormat* value)
        {
            std::string arg_stringified;
            value->AppendAsTraceFormat(&arg_stringified);
            stream_ << arg_stringified;
        }

        JSONTraceWriter::JSONTraceWriter(std::ostream& stream)
            : JSONTraceWriter(stream, "traceEvents")
        {
        }

        JSONTraceWriter::JSONTraceWriter(std::ostream& stream, const std::string& tag)
            : stream_(stream)
        {
            stream_ << "{\"" << tag << "\":[";
        }

        JSONTraceWriter::~JSONTraceWriter() { stream_ << "]}"; }

        void JSONTraceWriter::AppendTraceEvent(TraceObject* trace_event)
        {
            if (append_comma_)
                stream_ << ",";
            append_comma_ = true;
            stream_ << "{\"pid\":" << trace_event->pid()
                    << ",\"tid\":" << trace_event->tid()
                    << ",\"ts\":" << trace_event->ts()
                    << ",\"tts\":" << trace_event->tts() << ",\"ph\":\""
                    << trace_event->phase() << "\",\"cat\":\""
                    << TracingController::GetCategoryGroupName(
                           trace_event->category_enabled_flag())
                    << "\",\"name\":\"" << trace_event->name()
                    << "\",\"dur\":" << trace_event->duration()
                    << ",\"tdur\":" << trace_event->cpu_duration();
            if (trace_event->flags() & (TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT)) {
                stream_ << ",\"bind_id\":\"0x" << std::hex << trace_event->bind_id() << "\""
                        << std::dec;
                if (trace_event->flags() & TRACE_EVENT_FLAG_FLOW_IN) {
                    stream_ << ",\"flow_in\":true";
                }
                if (trace_event->flags() & TRACE_EVENT_FLAG_FLOW_OUT) {
                    stream_ << ",\"flow_out\":true";
                }
            }
            if (trace_event->flags() & TRACE_EVENT_FLAG_HAS_ID) {
                if (trace_event->scope() != nullptr) {
                    stream_ << ",\"scope\":\"" << trace_event->scope() << "\"";
                }
                // So as not to lose bits from a 64-bit integer, output as a hex string.
                stream_ << ",\"id\":\"0x" << std::hex << trace_event->id() << "\""
                        << std::dec;
            }
            stream_ << ",\"args\":{";
            const char** arg_names = trace_event->arg_names();
            const uint8_t* arg_types = trace_event->arg_types();
            TraceObject::ArgValue* arg_values = trace_event->arg_values();
            std::unique_ptr<v8::ConvertableToTraceFormat>* arg_convertables = trace_event->arg_convertables();
            for (int i = 0; i < trace_event->num_args(); ++i) {
                if (i > 0)
                    stream_ << ",";
                stream_ << "\"" << arg_names[i] << "\":";
                if (arg_types[i] == TRACE_VALUE_TYPE_CONVERTABLE) {
                    AppendArgValue(arg_convertables[i].get());
                } else {
                    AppendArgValue(arg_types[i], arg_values[i]);
                }
            }
            stream_ << "}}";
            // TODO(fmeawad): Add support for Flow Events.
        }

        void JSONTraceWriter::Flush() { }

        TraceWriter* TraceWriter::CreateJSONTraceWriter(std::ostream& stream)
        {
            return new JSONTraceWriter(stream);
        }

        TraceWriter* TraceWriter::CreateJSONTraceWriter(std::ostream& stream,
            const std::string& tag)
        {
            return new JSONTraceWriter(stream, tag);
        }

    } // namespace tracing
} // namespace platform
} // namespace v8
